// Tailwind-like breakpoint hook for Vue 3
// Returns a ref<string> among: "sm" | "md" | "lg" | "xl" | "2xl"
// Recalculates on resize and on mount. Uses Tailwind's default breakpoints.

import { ref, onMounted, onBeforeUnmount, computed } from "vue";

export type TailwindBreakpoint = "xs" | "sm" | "md" | "lg" | "xl" | "2xl";

// Tailwind default min-widths: https://tailwindcss.com/docs/responsive-design
const BREAKPOINTS: { key: TailwindBreakpoint; min: number }[] = [
  { key: "xs", min: 0 },
  { key: "sm", min: 640 },
  { key: "md", min: 768 },
  { key: "lg", min: 1024 },
  { key: "xl", min: 1280 },
  { key: "2xl", min: 1536 }
];

/**
 * Compute the Tailwind breakpoint key for a given width.
 * Note: widths below 640 are considered the base (no prefix). We map them to "sm"-1 for convenience,
 * but this hook returns the nearest defined key at or below current width.
 */
function getBreakpointKey(width: number): TailwindBreakpoint {
  let current: TailwindBreakpoint = "xs";
  for (const bp of BREAKPOINTS) {
    if (width >= bp.min) current = bp.key;
  }
  return current;
}

/**
 * useTailwindBreakpoint
 * - returns a ref of current Tailwind breakpoint key
 * - updates on window resize with a lightweight listener
 */
export function useTailwindBreakpoint() {
  const breakpoint = ref<TailwindBreakpoint>("xs");

  const recalc = () => {
    if (typeof window === "undefined") return;
    const width = window.innerWidth;
    breakpoint.value = getBreakpointKey(width);
  };

  onMounted(() => {
    recalc();
    window.addEventListener("resize", recalc, { passive: true });
  });

  onBeforeUnmount(() => {
    if (typeof window !== "undefined") {
      window.removeEventListener("resize", recalc);
    }
  });

  return breakpoint;
}

/**
 * Convenience booleans for common breakpoint queries.
 * Example usage:
 * const { isSmUp, isMdUp } = useBreakpointQuery();
 */
export function useBreakpointQuery() {
  const current = useTailwindBreakpoint();
  const order: TailwindBreakpoint[] = ["xs", "sm", "md", "lg", "xl", "2xl"];

  const isAtLeast = (key: TailwindBreakpoint) => () => {
    return order.indexOf(current.value) >= order.indexOf(key);
  };

  return {
    current,
    isSmUp: isAtLeast("sm"),
    isMdUp: isAtLeast("md"),
    isLgUp: isAtLeast("lg"),
    isXlUp: isAtLeast("xl"),
    is2xlUp: isAtLeast("2xl")
  };
}

/**
 * useResponsiveValue
 * Provide values per Tailwind breakpoint and get a reactive value that
 * follows the current breakpoint, falling back to the nearest previous
 * non-null value when needed.
 *
 * Example:
 * useResponsiveValue({ xs: 10, sm: 20, md: null, lg: null, xl: 30, 2xl: null })
 * - xs -> 10, sm -> 20, md -> 20, lg -> 20, xl -> 30, 2xl -> 30
 */
export function useResponsiveValue<T>(
  values: Partial<Record<TailwindBreakpoint, T | null>>
) {
  const current = useTailwindBreakpoint();
  const order: TailwindBreakpoint[] = ["xs", "sm", "md", "lg", "xl", "2xl"];

  const resolved = computed<T | null>(() => {
    const index = order.indexOf(current.value);
    for (let i = index; i >= 0; i--) {
      const key = order[i];
      const candidate = values[key];
      if (candidate !== undefined && candidate !== null) return candidate;
    }
    return null;
  });

  return resolved;
}

/**
 * example usage:
 * <script setup lang="ts">
 *  import { useBreakpointQuery } from "@/utils/device";
 *  const { current, isMdUp } = useBreakpointQuery();
 * </script>
 *
 * <template>
 *  <div>
 *   <p>Breakpoint: {{ current }}</p>
 *   <DesktopPanel v-if="isMdUp()" />
 *   <MobilePanel v-else />
 *  </div>
 * </template>
 */

/** 获取设备信息（浏览器信息） */
function hashStringFNV1a(str: string): string {
  let hash = 0x811c9dc5;
  for (let i = 0; i < str.length; i++) {
    hash ^= str.charCodeAt(i);
    hash = Math.imul(hash >>> 0, 0x01000193);
  }
  return (hash >>> 0).toString(16).padStart(8, "0");
}

function getBrowserShortName(ua: string): string {
  const isEdge = ua.includes("Edg/");
  const isOpera = ua.includes("OPR/");
  const isChrome =
    (ua.includes("Chrome/") || ua.includes("CriOS/")) && !isEdge && !isOpera;
  const isFirefox = ua.includes("Firefox/");
  const isSafari = ua.includes("Safari/") && !isChrome && !isEdge && !isOpera;
  if (isEdge) return "edge";
  if (isOpera) return "opera";
  if (isChrome) return "chrome";
  if (isFirefox) return "firefox";
  if (isSafari) return "safari";
  return "other";
}

function getOSShortName(ua: string, platform: string): string {
  if (/Windows|Win32|Win64/i.test(platform) || /Windows NT/i.test(ua))
    return "win";
  if (/Mac|MacIntel|MacPPC|Mac68K/i.test(platform) || /Mac OS X/i.test(ua))
    return "mac";
  if (/Android/i.test(ua)) return "android";
  if (/iPhone|iPad|iPod/i.test(ua)) return "ios";
  if (/Linux/i.test(platform) || /Linux/i.test(ua)) return "linux";
  return "other";
}

function formatTimezoneOffset(): string {
  const offsetMin = -new Date().getTimezoneOffset();
  const sign = offsetMin >= 0 ? "+" : "-";
  const abs = Math.abs(offsetMin);
  const hh = String(Math.floor(abs / 60)).padStart(2, "0");
  const mm = String(abs % 60).padStart(2, "0");
  return `${sign}${hh}${mm}`;
}

export function getDeviceInfo() {
  try {
    if (typeof window === "undefined" || typeof navigator === "undefined") {
      return "env=ssr";
    }
    const ua = navigator.userAgent || "";
    const platform = (navigator as any).platform || "";
    const br = getBrowserShortName(ua);
    const os = getOSShortName(ua, platform);
    const scrW = window.screen?.width ?? 0;
    const scrH = window.screen?.height ?? 0;
    const scr = scrW && scrH ? `${scrW}x${scrH}` : "";
    const dprNum =
      typeof window.devicePixelRatio === "number" ? window.devicePixelRatio : 1;
    const dprStr = Number.isFinite(dprNum)
      ? dprNum.toFixed(2).replace(/\.00$/, "")
      : "";
    const tz = formatTimezoneOffset();
    const lang = (navigator.language || "").toLowerCase();
    const mem = (navigator as any).deviceMemory
      ? String((navigator as any).deviceMemory)
      : "";
    const cpu = navigator.hardwareConcurrency
      ? String(navigator.hardwareConcurrency)
      : "";
    const touch =
      "maxTouchPoints" in navigator
        ? (navigator as any).maxTouchPoints > 0
          ? "1"
          : "0"
        : "";
    const uah = hashStringFNV1a(ua).slice(0, 8);

    const parts = [
      `br=${br}`,
      `os=${os}`,
      scr ? `scr=${scr}` : "",
      dprStr ? `dpr=${dprStr}` : "",
      `tz=${tz}`,
      lang ? `lang=${lang}` : "",
      mem ? `mem=${mem}` : "",
      cpu ? `cpu=${cpu}` : "",
      touch !== "" ? `touch=${touch}` : "",
      `uah=${uah}`
    ].filter(Boolean);

    return parts.join(";");
  } catch {
    return "env=err";
  }
}

export const isTouchDevice = () => {
  try {
    if (typeof navigator !== "undefined") {
      const anyNav = navigator as any;
      if ("maxTouchPoints" in anyNav) return anyNav.maxTouchPoints > 0;
    }
    if (typeof window !== "undefined") return "ontouchstart" in window;
  } catch {}
  return false;
};
